/*
 * Copyright 2012-2017 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.codestd.security.shiro.filter;

import com.codestd.security.shiro.token.CaptchaUsernamePasswordToken;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
import org.apache.shiro.web.util.WebUtils;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

/**
 * 处理Token
 *
 * @author Wang Chengwei(Jaune)
 * @since 1.0.0
 */
public class CaptchaFormAuthenticationFilter extends FormAuthenticationFilter {

    private static final String  DEFAULT_CAPTCHA_PARAM = "captcha";
    public static final String  ERROR_TIMES_KEY = "errorTimes";

    private String captchaParamName;
    private int maxErrorTimes = 3;

    public String getCaptchaParamName() {
        return this.captchaParamName != null ? this.captchaParamName : DEFAULT_CAPTCHA_PARAM;
    }

    public void setCaptchaParamName(String captchaParamName) {
        this.captchaParamName = captchaParamName;
    }

    public int getMaxErrorTimes() {
        return maxErrorTimes;
    }

    public void setMaxErrorTimes(int maxErrorTimes) {
        this.maxErrorTimes = maxErrorTimes;
    }

    protected AuthenticationToken createToken(String username, String password,
                                              ServletRequest request, ServletResponse response) {
        boolean rememberMe = isRememberMe(request);
        String host = this.getHost(request);
        String captcha = this.getCaptcha(request);
        CaptchaUsernamePasswordToken token =
                new CaptchaUsernamePasswordToken(username, password, rememberMe, host, captcha);
        if (this.needCheckCaptcha((HttpServletRequest) request)) {
            token.setNeedCheckCaptcha(true);
        }
        return token;
    }

    private String getCaptcha(ServletRequest request) {
        return WebUtils.getCleanParam(request, this.getCaptchaParamName());
    }

    @Override
    protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {
        // 密码错误抛出IncorrectCredentialsException异常，只有在密码输入错误时才累加错误次数。
        Integer errorTimes = null;
        if (e instanceof IncorrectCredentialsException) {
            errorTimes = this.incrementErrorTimes((HttpServletRequest) request);
        }
        // 如果是其他错误，需要重新获取错误次数
        if (errorTimes == null) {
            errorTimes = getErrorTimes((HttpServletRequest) request);
        }
        // 判断错误次数，如果错误次数超过maxErrorTimes，那么向页面转传递一个needCaptcha的参数
        if (errorTimes >= this.maxErrorTimes) {
            request.setAttribute("needCaptcha", true);
        }

        return super.onLoginFailure(token, e, request, response);
    }

    @Override
    protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request, ServletResponse response) throws Exception {
        this.clearErrorTimes(subject);
        return super.onLoginSuccess(token, subject, request, response);
    }

    /**
     * 累加错误次数
     */
    private Integer incrementErrorTimes(HttpServletRequest request) {
        HttpSession session = request.getSession();
        Integer errorTimes = (Integer) session.getAttribute(ERROR_TIMES_KEY);
        if (errorTimes == null) {
            errorTimes = 1;
        } else {
            errorTimes++;
        }
        session.setAttribute(ERROR_TIMES_KEY, errorTimes);
        return errorTimes;
    }

    /**
     * 清除错误次数
     */
    private void clearErrorTimes(Subject subject) {
        Session session = subject.getSession();
        session.removeAttribute(ERROR_TIMES_KEY);
    }

    /**
     * 判断是否需要校验验证码
     */
    private boolean needCheckCaptcha(HttpServletRequest request) {
        return this.getErrorTimes(request) >= this.maxErrorTimes;
    }

    /**
     * 获取登录错误的次数
     */
    private Integer getErrorTimes(HttpServletRequest request) {
        HttpSession session = request.getSession();
        Integer errorTimes = (Integer) session.getAttribute(ERROR_TIMES_KEY);
        if (errorTimes == null) {
            errorTimes = 0;
        }
        return errorTimes;
    }
}
